
#include "../iface/debug.h"
#include "../utils/StringUtils.h"
#include "CryptEChain.h"

// CRYPTLIB is the worst, most pedantic, most ridiculous API I've ever seen/used.
// Having to interject and give/receive data at random times dependent on envelope type is silly!

int CryptEChain::PrePushInterjection(CryptELink *clink)
{
   int status;
   switch (clink->intent)
   {
      case CRYPTCONTEXT_SIGN_PRIVATE_KEY:
      {
         int magictriesprv = 0;
         do
         {
            status = cryptSetAttribute(clink->envelope, CRYPT_ENVINFO_SIGNATURE, clink->cntxt);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriesprv++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriesprv < CRYPT_MAX_RETRIES));
         fCryptCheckError(status);
         clink->intent |= CRYPTCONTEXT_DONEINTERJECTION;
         break;
      }
      case CRYPTCONTEXT_PUBLIC_KEY_ENCRYPT:
      {
         int magictriespub = 0;
         do
         {
            status = cryptSetAttribute(clink->envelope, CRYPT_ENVINFO_PUBLICKEY, clink->cntxt);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriespub++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriespub < CRYPT_MAX_RETRIES));
         fCryptCheckError(status);
         clink->intent |= CRYPTCONTEXT_DONEINTERJECTION;
         break;
      }
   }
   return 0;
}

int CryptEChain::PrePopInterjection(CryptELink *clink)
{
   int status;
   switch (clink->intent)
   {
      case CRYPTCONTEXT_PRIVATE_KEY_DECRYPT:
      {
         int magictriesprv = 0;
         do
         {
            status = cryptSetAttribute(clink->envelope, CRYPT_ENVINFO_PRIVATEKEY, clink->cntxt);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriesprv++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriesprv < CRYPT_MAX_RETRIES));
         fCryptCheckError(status);
         if (status == CRYPT_ERROR_PERMISSION) return -1;
         if (status == CRYPT_ERROR_WRONGKEY) return -1;
         clink->intent |= CRYPTCONTEXT_DONEINTERJECTION;
         break;
      }
   }
   return 0;
}

int CryptEChain::PostFlushInterjection(CryptELink *clink)
{
   int status;
   switch (clink->intent)
   {
      case CRYPTCONTEXT_CHECKSIG_PUBLIC_KEY:
      {
         int minitrusted = 1;
         int signatureresult = 0;
         int magictriessig = 0;
         do
         {
            status = cryptSetAttribute(clink->envelope, CRYPT_ENVINFO_SIGNATURE, clink->cntxt);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriessig++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriessig < CRYPT_MAX_RETRIES));
         if (status == CRYPT_ERROR_WRONGKEY) return -1;
         fCryptCheckError(status);
         int magictriesres = 0;
         do
         {
            status = cryptGetAttribute(clink->envelope, CRYPT_ENVINFO_SIGNATURE_RESULT, &signatureresult);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriesres++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriesres < CRYPT_MAX_RETRIES));
         if ((cryptStatusError(status)) || (signatureresult != CRYPT_OK))
            minitrusted = 0;
         fCryptCheckError(status);
         trusted = minitrusted;
         clink->intent |= CRYPTCONTEXT_DONEINTERJECTION;
         break;
      }
   }
   return 0;
}

CryptEChain::CryptEChain()
{
   root = NULL;
   firstbuf = NULL;
   firstbib = 0;
   trusted = 2;
}

CryptEChain::~CryptEChain()
{
   int status;
   if (root == NULL) return;
   while (root != NULL)
   {
      CryptELink *old = root;
      root = root->inward;
      status = cryptDestroyEnvelope(old->envelope);
      fCryptCheckError(status);
      if (old->cntxt != 0)
      {
         status = cryptDestroyContext(old->cntxt);
         fCryptCheckError(status);
      }
      if (old->buf) free(old->buf);
      free(old);
   }
   if (firstbuf) free(firstbuf);
}

int64_t CryptEChain::ProcessIteration(APRBucketBrigade *inp, char *dbuffer, int imaxdata)
{
   int finalretval = 0;
   int maxdata = ((imaxdata > ENV_READ_BUFFER) ? ENV_READ_BUFFER : imaxdata);

   int status;
   int closedbefore = 0;
   int movement = 0;
   CryptELink *innermost = root;
   if (root == NULL)
   {
      errprint("Tried to use a chain with no envelopes.");
      return -1;
   }
   while (innermost->inward != NULL) innermost = innermost->inward;
   CryptELink *thislink = innermost;
   char **lastlink = &firstbuf;
//   printf("firstbib: %d\n", firstbib);
   int *lastbib = &firstbib;
   if (thislink != NULL)
   {
      char *newbuffer = (char *)malloc(sizeof(char)*(maxdata + (*lastbib)));
      memcpy(newbuffer, *lastlink, sizeof(char)*(*lastbib));
      free(*lastlink);
      (*lastlink) = newbuffer;
      int bytesactuallypopped = inp->Pop(&(newbuffer[*lastbib]), maxdata);
      if (bytesactuallypopped > 0)
         (*lastbib) += bytesactuallypopped;
      if (inp->IsClosed()) closedbefore = 1;
      movement += bytesactuallypopped;
      if (movement) closedbefore = 0;
//      printf("firstbib2: %d\n", *lastbib);
   }
//   if ((*lastbib) == 42626)
//      printf("Break point here!");
//   if (((*lastbib) < ENV_READ_BUFFER) && (closedbefore == 0)) return 0;
   while (thislink != NULL)
   {
      if ((*lastbib) > 0)
      {
         if (PrePushInterjection(thislink) < 0) return -1;
         int bytesactuallypushed = 0;
         int magictriespush = 0;
         do
         {
            status = cryptPushData(thislink->envelope, (*lastlink), (*lastbib), &bytesactuallypushed);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriespush++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriespush < CRYPT_MAX_RETRIES));
//         printf("l %d %d %d\n", *lastbib, bytesactuallypushed, thislink->intent);
         if ((status == CRYPT_ENVELOPE_RESOURCE) && (thislink->intent == CRYPTCONTEXT_PRIVATE_KEY_DECRYPT))
            status = CRYPT_OK;
         if (status == CRYPT_ERROR_COMPLETE)
            status = CRYPT_OK;
         if (status != CRYPT_OK)
         {
            errprint("Problem with CryptELink of type %d (last bytes pushed %d, bytes pushed %d)", thislink->intent, (*lastbib), bytesactuallypushed);
            CryptInterface::GetErrorDetailObject(thislink->cntxt);
            CryptInterface::GetErrorDetailObject(thislink->envelope);
         }
         fCryptCheckError(status);
         char *newbuffer = (char *)malloc(sizeof(char)*((*lastbib) - bytesactuallypushed));
         memcpy(newbuffer, &((*lastlink)[bytesactuallypushed]), sizeof(char)*((*lastbib) - bytesactuallypushed));
         free(*lastlink);
         (*lastlink) = newbuffer;
         (*lastbib) -= bytesactuallypushed;
         movement += bytesactuallypushed;
      }
      closedbefore = ((closedbefore) && (movement == 0));
      if (closedbefore)
      {
         int magictriesflush = 0;
         do
         {
            status = cryptFlushData(thislink->envelope);
            if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
            magictriesflush++;
         } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriesflush < CRYPT_MAX_RETRIES));
         fCryptCheckError(status);
         if (PostFlushInterjection(thislink) < 0) return -1;
      }
      int bytesactuallypopped = 0;
      char *newbuffer = (char *)malloc(sizeof(char)*(maxdata + thislink->bytesinbuf));
      memcpy(newbuffer, thislink->buf, sizeof(char)*(thislink->bytesinbuf));
      free(thislink->buf);
      thislink->buf = newbuffer;
      if (PrePopInterjection(thislink) < 0) return -1;
      int magictriespop = 0;
      do
      {
         status = cryptPopData(thislink->envelope, &(thislink->buf[thislink->bytesinbuf]), maxdata, &bytesactuallypopped);
         if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
         magictriespop++;
      } while ((status == CRYPT_ERROR_TIMEOUT) && (magictriespop < CRYPT_MAX_RETRIES));
//      printf("p %d %d %d %d %d\n", *lastbib, bytesactuallypopped, thislink->intent, thislink->bytesinbuf, closedbefore);
      fCryptCheckError(status);
      if (status == CRYPT_ERROR_SIGNATURE) return -1;
      //if (status == CRYPT_ERROR_BADDATA) return -1;
      //if (status != 0) return -1;
      if (bytesactuallypopped > 0)
         thislink->bytesinbuf += bytesactuallypopped;
      movement += bytesactuallypopped;
      lastlink = &(thislink->buf);
      lastbib = &(thislink->bytesinbuf);
      thislink = thislink->outward;
   }
   int bytesactuallypushed = 0;
   if ((*lastbib) > 0)
   {
      int real_bytes = ((*lastbib) > maxdata) ? maxdata : (*lastbib);
      memcpy(dbuffer, (*lastlink), real_bytes);
      bytesactuallypushed = real_bytes; finalretval = real_bytes;
      char *newbuffer = (char *)malloc(sizeof(char)*((*lastbib) - bytesactuallypushed));
      memcpy(newbuffer, &((*lastlink)[bytesactuallypushed]), sizeof(char)*((*lastbib) - bytesactuallypushed));
      free(*lastlink);
      (*lastlink) = newbuffer;
      (*lastbib) -= bytesactuallypushed;
      movement += bytesactuallypushed;
   }
   closedbefore = ((closedbefore) && (movement == 0));
   if (closedbefore) return -1;
   return finalretval;
}

int CryptEChain::ProcessIteration(APRBucketBrigade *inp, APRBucketBrigade *out)
{
   char *dbuffer = (char *)malloc(sizeof(char)*ENV_READ_BUFFER);
   int64_t popped = ProcessIteration(inp, dbuffer, ENV_READ_BUFFER);
   if (popped == -1)
   {
      out->Close();
      free(dbuffer);
      return 1;
   }
   out->Write(dbuffer, (apr_size_t)popped);
   free(dbuffer);
   return 0;
}

void CryptEChain::Process(APRBucketBrigade *inp, APRBucketBrigade *out)
{
   int proc = 0;
   while (!proc)
      proc = ProcessIteration(inp, out);
}

void CryptEChain::AddEnvelope(CRYPT_ENVELOPE nenvelope, CRYPT_CONTEXT icntxt, int iintent)
{
   CryptELink *newlink = (CryptELink *)malloc(sizeof(CryptELink));
   newlink->intent = iintent;
   newlink->envelope = nenvelope;
   newlink->cntxt = icntxt;
   newlink->inward = NULL;
   newlink->outward = NULL;
   newlink->bytesinbuf = 0;
   newlink->buf = NULL;

   CryptELink *curr = root;
   if (curr == NULL)
   {
      root = newlink;
   } else
   {
      root = newlink;
      newlink->inward = curr;
      curr->outward = root;
   }
}

CRYPT_ENVELOPE CryptEChain::EncodingEnvelope()
{
   int tries = 0;
   int status;
   CRYPT_ENVELOPE tenvelope;
   do
   {
      status = cryptCreateEnvelope(&tenvelope, CRYPT_UNUSED, CRYPT_FORMAT_CRYPTLIB); // default
      if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
      tries++;
   } while ((status == CRYPT_ERROR_TIMEOUT) && (tries < CRYPT_MAX_RETRIES));
   fCryptCheckError(status);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("ENV: %d\n", tenvelope);
#endif
   if (tenvelope == -1)
   {
      errprint("envelope has invalid handle");
      CryptInterface::GetErrorDetailObject(tenvelope);
   }
   status = cryptSetAttribute(tenvelope, CRYPT_ATTRIBUTE_BUFFERSIZE, (ENV_READ_BUFFER*3)/2);
   fCryptCheckError(status);
   return tenvelope;
}

CRYPT_ENVELOPE CryptEChain::DecodingEnvelope()
{
   int tries = 0;
   int status;
   CRYPT_ENVELOPE tenvelope;
   do
   {
      status = cryptCreateEnvelope(&tenvelope, CRYPT_UNUSED, CRYPT_FORMAT_AUTO); // default
      if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
      tries++;
   } while ((status == CRYPT_ERROR_TIMEOUT) && (tries < CRYPT_MAX_RETRIES));
   fCryptCheckError(status);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("ENV: %d\n", tenvelope);
#endif
   if (tenvelope == -1)
   {
      errprint("envelope has invalid handle");
      CryptInterface::GetErrorDetailObject(tenvelope);
   }
   status = cryptSetAttribute(tenvelope, CRYPT_ATTRIBUTE_BUFFERSIZE, (ENV_READ_BUFFER*3)/2);
   fCryptCheckError(status);
   return tenvelope;
}

CRYPT_ENVELOPE CryptEChain::GenerateCompressEnv()
{
   int status;
   CRYPT_ENVELOPE tenvelope = CryptEChain::EncodingEnvelope();
   status = cryptSetAttribute(tenvelope, CRYPT_ENVINFO_COMPRESSION, CRYPT_UNUSED);
   fCryptCheckError(status);
   return tenvelope;
}

CRYPT_ENVELOPE CryptEChain::GenerateDecompressEnv()
{
   int tries = 0;
   int status;
   CRYPT_ENVELOPE tenvelope;
   do
   {
      status = cryptCreateEnvelope(&tenvelope, CRYPT_UNUSED, CRYPT_FORMAT_AUTO); // default
      if (status == CRYPT_ERROR_TIMEOUT) apr_sleep(rand() % CRYPT_MAX_TIMESLEEP);
      tries++;
   } while ((status == CRYPT_ERROR_TIMEOUT) && (tries < CRYPT_MAX_RETRIES));
   fCryptCheckError(status);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("ENV: %d\n", tenvelope);
#endif
   if (tenvelope == -1)
   {
      errprint("envelope has invalid handle");
      CryptInterface::GetErrorDetailObject(tenvelope);
   }
   // Serious problems with blocked pipes for large quantities of decompression
   status = cryptSetAttribute(tenvelope, CRYPT_ATTRIBUTE_BUFFERSIZE, 1048576*2);
   fCryptCheckError(status);
   return tenvelope;
}

void CryptEChain::BuildBasicChain(const char *cname, const char *sname, KeyPair *crypto, KeyPair *signing, int dodecrypt, int docompress)
{
   BuildBasicChain2(cname, sname, crypto, signing, dodecrypt, docompress, -1);
}

void CryptEChain::BuildBasicChain2(const char *cname, const char *sname, KeyPair *crypto, KeyPair *signing, int dodecrypt, int docompress, int inputsize)
{
   char *spub_cntxt_name = StringUtils::varprintf("pub_%s", (sname) ? sname : "key");
   char *cpub_cntxt_name = StringUtils::varprintf("pub_%s", (cname) ? cname : "key");
   char *sprv_cntxt_name = StringUtils::varprintf("prv_%s", (sname) ? sname : "key");
   char *cprv_cntxt_name = StringUtils::varprintf("prv_%s", (cname) ? cname : "key");
   if (dodecrypt)
   {
      if (signing)
      {
         CRYPT_CONTEXT tcntxt = signing->GetCryptContext_PublicKey(spub_cntxt_name);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("CNTXT: %d\n", tcntxt);
#endif
         AddEnvelope(DecodingEnvelope(), tcntxt, CRYPTCONTEXT_CHECKSIG_PUBLIC_KEY);
      }
      if (crypto)
      {
         CRYPT_CONTEXT tcntxt = crypto->GetCryptContext_PrivateKey(cprv_cntxt_name);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("CNTXT: %d\n", tcntxt);
#endif
         AddEnvelope(DecodingEnvelope(), tcntxt, CRYPTCONTEXT_PRIVATE_KEY_DECRYPT);
      }
      if (docompress) AddEnvelope(GenerateDecompressEnv(), 0, CRYPTCONTEXT_DECOMPRESS);
   } else
   {
      if (docompress) AddEnvelope(GenerateCompressEnv(), 0, CRYPTCONTEXT_COMPRESS);
      if (crypto)
      {
         CRYPT_CONTEXT tcntxt = crypto->GetCryptContext_PublicKey(cpub_cntxt_name);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("CNTXT: %d\n", tcntxt);
#endif
         CRYPT_ENVELOPE tenvelope = EncodingEnvelope();
         int status;
         status = cryptSetAttribute(tenvelope, CRYPT_ENVINFO_INTEGRITY, CRYPT_INTEGRITY_FULL);
         fCryptCheckError(status);
         AddEnvelope(tenvelope, tcntxt, CRYPTCONTEXT_PUBLIC_KEY_ENCRYPT);
      }
      if (signing)
      {
         CRYPT_CONTEXT tcntxt = signing->GetCryptContext_PrivateKey(sprv_cntxt_name);
#ifdef DEBUG_CRYPTLIB_OBJECTS
   printf("CNTXT: %d\n", tcntxt);
#endif
         AddEnvelope(EncodingEnvelope(), tcntxt, CRYPTCONTEXT_SIGN_PRIVATE_KEY);
      }
   }
   free(spub_cntxt_name);
   free(cpub_cntxt_name);
   free(sprv_cntxt_name);
   free(cprv_cntxt_name);
}

void CryptEChain::BuildEncryptionChain2(EncryptionKey *crypto, SigningKey *signing, int docompress, int inputsize)
{
   if (docompress) AddEnvelope(GenerateCompressEnv(), 0, CRYPTCONTEXT_COMPRESS);
   if (crypto)
   {
      CRYPT_CONTEXT tcntxt = -1;
      crypto->GetCryptlibEncryptionContext(&tcntxt);
#ifdef DEBUG_CRYPTLIB_OBJECTS
printf("CNTXT: %d\n", tcntxt);
#endif
      CRYPT_ENVELOPE tenvelope = EncodingEnvelope();
      int status;
      status = cryptSetAttribute(tenvelope, CRYPT_ENVINFO_INTEGRITY, CRYPT_INTEGRITY_FULL);
      fCryptCheckError(status);
      AddEnvelope(tenvelope, tcntxt, CRYPTCONTEXT_PUBLIC_KEY_ENCRYPT);
   }
   if (signing)
   {
      CRYPT_CONTEXT tcntxt = -1;
      signing->GetCryptlibSigningContext(&tcntxt);
#ifdef DEBUG_CRYPTLIB_OBJECTS
printf("CNTXT: %d\n", tcntxt);
#endif
      AddEnvelope(EncodingEnvelope(), tcntxt, CRYPTCONTEXT_SIGN_PRIVATE_KEY);
   }
}

void CryptEChain::BuildDecryptionChain2(DecryptionKey *crypto, CheckingKey *signing, int dodecompress, int inputsize)
{
   if (signing)
   {
      CRYPT_CONTEXT tcntxt = -1;
      signing->GetCryptlibCheckingContext(&tcntxt);
#ifdef DEBUG_CRYPTLIB_OBJECTS
printf("CNTXT: %d\n", tcntxt);
#endif
      AddEnvelope(DecodingEnvelope(), tcntxt, CRYPTCONTEXT_CHECKSIG_PUBLIC_KEY);
   }
   if (crypto)
   {
      CRYPT_CONTEXT tcntxt = -1;
      crypto->GetCryptlibDecryptionContext(&tcntxt);
#ifdef DEBUG_CRYPTLIB_OBJECTS
printf("CNTXT: %d\n", tcntxt);
#endif
      AddEnvelope(DecodingEnvelope(), tcntxt, CRYPTCONTEXT_PRIVATE_KEY_DECRYPT);
   }
   if (dodecompress) AddEnvelope(GenerateDecompressEnv(), 0, CRYPTCONTEXT_DECOMPRESS);
}
